-module(queuemanager).
-export([start/0, kill/0]).
-import(werkzeug,[logging/2, to_String/1, timeMilliSecond/0]).
-define(EMPTY_MESSAGE, {0,"No message available!"}).

start() -> 
	register(queuemanager,spawn(fun() -> main() end)).


main() -> 
	{_,Config} = file:consult("../config/server.cfg"),
	main({[],[]},proplists:get_value(dlqlimit,Config)).

main({HBQ, DLQ}, DLQSize) ->
	receive
		{dropmessage, {Nachricht, NNr}} -> 
			case NNr > dlq_max(DLQ) of
				true -> 
					NewMessage = strings:concat([Nachricht, "HBQ In: ", timeMilliSecond(), "| "]),
					log(strings:concat([NewMessage, "-dropmessage"])),
					main(validate_queues({[{NNr, NewMessage} | HBQ], DLQ}, DLQSize), DLQSize);
				false -> 
					log(strings:concat(["!!!! Nachricht mit ungültger NachrichtenID(",to_String(NNr),") erhalten: <<<", Nachricht, ">>>"])),
					main({HBQ, DLQ}, DLQSize)
			end;
		{getmessages, NNr, PID} ->
			queuemanager ! {getmessages, NNr, PID}, 
			main(whereis(clientmanager), {HBQ, DLQ}, DLQSize);
		kill_qm -> unregister(queuemanager)
	end.

%Nachdem der Clientmanager zum ersten mal eine Nachricht an den 
%Queuemanager gesendet hat, speichert dieser dessen PID, um beim
%Beenden keine Probleme zu bekommen.
main(CM, {HBQ, DLQ}, DLQSize) -> 
	receive
		{dropmessage, {Nachricht, NNr}} -> 
			case NNr > dlq_max(DLQ) of
				true -> 
					NewMessage = strings:concat([Nachricht, "HBQ In: ", timeMilliSecond(), "| "]),
					log(strings:concat([NewMessage, "-dropmessage"])),
					main(CM, validate_queues({[{NNr, NewMessage} | HBQ], DLQ}, DLQSize), DLQSize);
				false -> 
					log(strings:concat(["!!!! Nachricht mit ungültger NachrichtenID(",to_String(NNr),") erhalten: <<<", Nachricht, ">>>"])),
					main(CM, {HBQ, DLQ}, DLQSize)
			end;
		{getmessages, LastNNr, PID} ->
			{{NNr,Message}, Rest} = get_next_message(DLQ, LastNNr),
			PID ! {reply, NNr, Message, Rest =< 1},
			log(strings:concat([Message," (",to_String(NNr), ")-getmessages"])),
			CM ! {sendLastNNrForPID, NNr, PID},
			main(CM, {HBQ, DLQ}, DLQSize);
		kill_qm -> unregister(queuemanager)
	end.


	
%Bestimmt die höchste in der Deliveryqueue vorhandene Nachrichtenid
dlq_max([]) -> 0;
dlq_max(DLQ) ->
	lists:max(proplists:get_keys(DLQ)).

dlq_min(DLQ) -> hbq_min(DLQ).

%Gibt den kleinsten NachrichtenIndex ab einem gewissen Index
dlq_min_from(DLQ, From) ->
	Remaining = lists:filter(fun({NNr,_}) -> NNr > From end, DLQ),
	{dlq_min(Remaining), length(Remaining)}.

hbq_min([]) -> 0;
hbq_min(HBQ) ->
	lists:min(proplists:get_keys(HBQ)).

log(Text) ->
	logging("../NServer.log", strings:concat([Text,"\n"])).

%Beendet den Queuemanager
kill() ->
	queuemanager ! kill_qm. 

%Prüft die Axiome der HBQ und DLQ und führt entsprechende Aktionen
%aus, um einen gültigen Zustand sicherzustellen.
validate_queues(Queues, DLQSize) ->
	resize_dlq(flush_hbq(fill_gap(flush_hbq(Queues), DLQSize)), DLQSize).

%Verschiebt, wenn möglich Nachrichten aus der HBQ in die DLQ
flush_hbq({HBQ,DLQ}) ->
	MinHBQ = hbq_min(HBQ),
	case MinHBQ == dlq_max(DLQ)+1 of
		true -> flush_hbq({proplists:delete(MinHBQ,HBQ), [{MinHBQ, strings:concat([proplists:get_value(MinHBQ, HBQ),"DLQ In: ", timeMilliSecond(), "|. "])} | DLQ]});
		false -> {HBQ,DLQ}
	end.

%Erzeugt, wenn nötig eine Fehlernachricht, die eine Lücke füllt
fill_gap({HBQ,DLQ}, DLQSize) ->
	case length(HBQ)*2 > DLQSize of 
		false -> {HBQ,DLQ};
		true -> {HBQ, [{hbq_min(HBQ)-1,strings:concat(["Fehlernachricht für die Nachrichten ",to_String(dlq_max(DLQ)+1), " bis ", to_String(hbq_min(HBQ)-1)])}|DLQ]}
	end.

%Löscht so lange die Nachrichten mit der kleinsten NachrichtenId
%Aus der DLQ, bis die DLQ wieder die korrekte Größe hat
resize_dlq({HBQ,DLQ}, DLQSize) ->
	case length(DLQ) > DLQSize of
		false -> {HBQ,DLQ};
		true -> resize_dlq({HBQ,proplists:delete(dlq_min(DLQ), DLQ)}, DLQSize)
	end.


%Gibt die nächste Nachricht aus der DeliveryQueue aus, die ab der NachrichtenNummer verfügbar ist.
get_next_message(DLQ, Nnr) ->
	case dlq_min_from(DLQ,Nnr) of 
		{0, 0} -> {?EMPTY_MESSAGE, 0};
		{Nr,N} -> {{Nr,proplists:get_value(Nr,DLQ)}, N}
	end.
